package com.xiaozhu.utils;

// FastJSON 序列化和反序列化

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类，封装常用 Redis 操作
 * 支持：
 * 1. 对象存储（自动 JSON 序列化）
 * 2. 泛型对象获取（TypeReference 解决编译擦除问题）
 * 3. 批量删除（SCAN 模式）
 * 4. 设置过期时间
 */
@Component // Spring 容器管理该类
public class RedisUtils {

    // 注入 Spring 提供的 StringRedisTemplate，用于操作 Redis
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // ==================== 对象存储 ====================

    /**
     * 将对象存储到 Redis（永久有效）
     * @param key Redis key
     * @param value 任意 Java 对象
     */
    public void set(String key, Object value){
        // 将对象序列化为 JSON 字符串
        String result = jsonFlied(value);

        // 使用 Redis 字符串操作存储
        stringRedisTemplate.opsForValue().set(key, result);
    }

    /**
     * 将对象存储到 Redis，并设置过期时间
     * @param key Redis key
     * @param value 任意对象
     * @param expire 过期时间
     * @param timeUnit 时间单位
     */
    public void set(String key, Object value, Long expire, TimeUnit timeUnit){
        // 序列化对象
        String result = jsonFlied(value);

        // 存储到 Redis，并设置过期时间
        stringRedisTemplate.opsForValue().set(key, result, expire, timeUnit);
    }

    // ==================== 对象读取 ====================

    /**
     * 获取 Redis 中 JSON 格式对象
     * @param key Redis key
     * @return FastJSON JSON 对象
     */
    public JSON get(String key){
        // 获取字符串
        String resultJson = stringRedisTemplate.opsForValue().get(key);

        if (resultJson != null){
            // 解析为 JSON 对象
            return parseFiled(resultJson);
        }
        return null; // key 不存在返回 null
    }

    /**
     * 获取指定类型的对象
     * @param key Redis key
     * @param tClass 对象类型
     * @param <T> 泛型类型
     * @return 反序列化后的对象
     */
    public <T> T get(String key, Class<T> tClass){
        // 从 Redis 获取 JSON 字符串
        String resultJson = stringRedisTemplate.opsForValue().get(key);

        if (resultJson != null){
            // 将 JSON 反序列化为指定类型对象
            return parseFiled(resultJson, tClass);
        }
        return null;
    }

    /**
     * 获取带泛型的对象（List<String> / Map<String,Object>）
     * @param key Redis key
     * @param tTypeReference TypeReference 保留泛型信息
     * @param <T> 泛型类型
     * @return 反序列化后的对象
     */
    public <T> T get(String key, TypeReference<T> tTypeReference){
        // 获取 Redis 中 JSON 字符串
        String resultJson = stringRedisTemplate.opsForValue().get(key);

        if (resultJson != null){
            // 反序列化为泛型对象
            return parseFiled(resultJson, tTypeReference);
        }

        return null;
    }

    // ==================== 批量删除 ====================

    /**
     * 根据匹配模式删除 Redis key（SCAN 遍历）
     * SCAN 是非阻塞、增量迭代
     * @param pattern key 匹配模式，如 "*" 或 "logs:*"
     */
    public void delByScan(String pattern){
        // 构建 SCAN 选项
        ScanOptions scanOptions = ScanOptions.scanOptions()
                .match(pattern) // 匹配指定模式
                .count(100)     // 每次扫描 100 个 key
                .build();

        try {
            // 使用 StringRedisTemplate 的 scan 方法
            stringRedisTemplate.execute((RedisConnection connection) -> {
                Cursor<byte[]> cursor = connection.commands().scan(scanOptions);
                
                // 遍历匹配到的 key
                while (cursor.hasNext()) {
                    byte[] keyBytes = cursor.next();
                    String keyName = new String(keyBytes);
                    
                    // 删除 key
                    stringRedisTemplate.delete(keyName);
                }
                
                cursor.close();
                return null;
            });

        } catch (Exception e) {
            throw new RuntimeException("删除 Redis key 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除 Redis 所有 key
     * ⚠️ 生产环境慎用，可能阻塞 Redis
     */
    public void delAll(){
        // 获取所有 key
        Set<String> keys = stringRedisTemplate.keys("*");

        // 非空判断
        if (keys != null && !keys.isEmpty()){
            stringRedisTemplate.delete(keys);
        }
    }

    // ==================== 内部工具方法 ====================

    /**
     * 将 Java 对象序列化为 JSON 字符串
     * @param value 任意对象
     * @return JSON 字符串
     */
    private String jsonFlied(Object value){
        return JSON.toJSONString(value);
    }

    /**
     * JSON 字符串 -> FastJSON JSON 对象
     * @param value JSON 字符串
     * @return JSON 对象
     */
    private JSON parseFiled(String value){
        return JSON.parseObject(value);
    }

    /**
     * JSON 字符串 -> 指定类型对象
     * @param value JSON 字符串
     * @param tClass 对象类型
     * @param <T> 泛型类型
     * @return 对象
     */
    private <T> T parseFiled(String value, Class<T> tClass){
        return JSON.parseObject(value, tClass);
    }
    /**
     * JSON 字符串 -> 泛型对象
     * JVM 在编译时会进行 **类型擦除**：
     *  - List<String> -> List
     *  - Map<String,Object> -> Map
     * TypeReference 利用匿名子类保留泛型签名，JSON 库通过反射解析
     * @param key JSON 字符串
     * @param typeReference TypeReference 对象
     * @param <T> 泛型类型
     * @return 泛型对象
     */
    private <T> T parseFiled(String key, TypeReference<T> typeReference){
        return JSON.parseObject(key, typeReference);
    }

    /**
     * 尝试获取分布式锁
     * @param key 锁的唯一标识，一般格式为"lock:业务名称：参数"，列如: lock:ChatId:chatType
     * @param requested 当前请求的唯一标识，用来标记当前是那一个会话或者哪一个用户持有当前的锁，方便在解锁时验证防止误删锁
     * @param timeoutMs 锁的自动过期时间（单位微秒）超时后 redis会自动删除key, 避免死锁
     * @return 加锁成功true(当前线程获取执行权) 加锁失败false（锁被其他线程获取）
     */
    public boolean tryLock(String key, String requested, long timeoutMs){
        // 凡是Boolean返回值不确定的时，用Boolean.TRUE.equals(xxx)永不为空指针
        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue()
                // setIfAbsent = SETNX 命令
                // 含义：只有当 key 不存在时，才会设置成功
                // 参数含义：
                //   key：锁的名称（唯一标识）
                //   requested：锁的值（用来标识当前是谁持有这把锁）
                //   timeoutMs：锁的过期时间
                //   TimeUnit.MICROSECONDS：过期时间单位（这里是微秒）
                .setIfAbsent(key,requested,timeoutMs, TimeUnit.MICROSECONDS));
    }

    /**
     * 安全释放锁
     * @param key 锁的唯一标识
     * @param requestId 当前线程唯一标识
     */
    /**
     * 安全释放分布式锁
     *
     * ⚙️ 背景：
     *   在 Redis 分布式锁中，每个线程或服务实例在加锁时，会为锁设置一个唯一的标识（requestId），
     *   例如 UUID，用来区分是哪一个线程/请求持有这把锁。
     *   解锁时必须校验当前锁是否属于自己，防止误删他人的锁。
     *
     * @param key        锁的唯一标识（对应 tryLock 时使用的 key）
     *                   通常格式为：lock:业务名:参数
     *                   例如：lock:getChtIds:chatTypeA
     * @param requestId  当前线程或服务实例的唯一标识（加锁时传入的 UUID）
     */
    public void unlock(String key, String requestId) {
        // 1️⃣ 从 Redis 获取当前锁对应的值（即持锁者标识）
        //    如果 Redis 中还存在这个 key，说明锁仍然有效；
        //    如果返回 null，说明锁已经过期或被释放。
        String value = stringRedisTemplate.opsForValue().get(key);

        // 2️⃣ 校验：只有当当前线程持有的 requestId 与 Redis 中存储的完全一致时，才能解锁
        //    equalsIgnoreCase()：忽略大小写比较，避免不同 UUID 大小写差异导致解锁失败
        if (requestId.equalsIgnoreCase(value)) {

            // 3️⃣ 删除锁（即删除 Redis 中的 key），释放资源
            //    ⚠️ 只有锁的持有者才能删除，确保线程安全
            stringRedisTemplate.delete(key);
        }

        // 🔚 注意：
        // 如果 requestId 不匹配，则说明锁不是当前线程加的，
        // 这时不应执行删除操作，避免“误删”其他线程/服务的锁。
    }

}
